/////////////////////////////////////////////////////////////////////////////
// Name:        ax_resource_library.cpp
// Purpose:     Resource library data structures
// Author:      Julian Smart
// Modified by:
// Created:     2008-11-18
// RCS-ID:      $Id$
// Copyright:   (c) Julian Smart
// Licence:     New BSD License
/////////////////////////////////////////////////////////////////////////////

#include "wx/wx.h"

#include "wx/xml/xml.h"
#include "wx/datstrm.h"
#include "wx/arrimpl.cpp"
#include "wx/wfstream.h"
#include "wx/txtstrm.h"
#include "wx/zipstrm.h"
#include "wx/textfile.h"
#include "wx/dir.h"
#include "wx/fileconf.h"

#include "ax_resource/ax_resource_library.h"
#include "ax_resource/ax_module.h"

#include "ax/ax_utils.h"
#include "variantutils/variantstream.h"

#include "ax/bitmaps/blankcover.xpm"

WX_DEFINE_OBJARRAY(AxResourceInfoArray)

WX_DEFINE_OBJARRAY(AxFolders)

/*
 * Basic information about a book
 */

AxResourceInfo::AxResourceInfo()
{
    Init();
}

void AxResourceInfo::Init()
{
    m_deleted = false;
    //m_bookType = (int) AxResourceInfoEbook;
    //m_bookStatus = (int) AxResourceInfoOwned;
    m_recordIdentifier = 0;    
    m_resource = NULL;
    m_ownsResource = false;
}

AxResourceInfo::~AxResourceInfo()
{
    Clear();
}

// Clear all
void AxResourceInfo::Clear()
{
    if (m_ownsResource && m_resource)
    {
        delete m_resource;
        m_resource = NULL;
    }

    Init();

    // m_thumbnailImageBlock.Clear();
    // m_coverDesign.Clear();
}

void AxResourceInfo::Copy(const AxResourceInfo& info)
{
    m_deleted = info.m_deleted;
    m_recordIdentifier = info.m_recordIdentifier;
    m_resource = info.m_resource;
}
    
bool AxResourceInfo::operator==(const AxResourceInfo& WXUNUSED(info)) const
{
    // TODO
    return false;
}

bool AxResourceInfo::Read(wxDataStreamHelper& helper)
{
    if (m_resource)
    {
        delete m_resource;
        m_resource = NULL;
    }

    wxString className;

    wxVariant attr;
    while (helper.Read(attr))
    {
        wxString name(attr.GetName());
        if (name == wxT("record-identifier"))
            m_recordIdentifier = attr.GetLong();
        else if (name == wxT("class-name"))
            className = attr.GetString();
        else if (name == wxT("deleted"))
            m_deleted = attr.GetBool();
    }

    if (!m_deleted)
    {
        if (!className.IsEmpty())
        {
            wxObject* obj  = wxCreateDynamicObject(className);
            if (!obj)
                return false;

            m_resource = wxDynamicCast(obj, AxResource);
            if (!m_resource)
            {
                delete obj;
                return false;
            }
        }
        
        if (m_resource)
            return m_resource->Read(helper);
        else
            return false;
    }
    else
        return true;
}

bool AxResourceInfo::Write(wxDataStreamHelper& helper)
{
    if (m_resource)
        helper.Write(wxString(m_resource->GetClassInfo()->GetClassName()), wxT("class-name"));
    helper.Write(m_recordIdentifier, wxT("record-identifier"));
    helper.Write(m_deleted, wxT("deleted"));
    helper.WriteEnd();

    if (m_resource && !m_deleted)
    {
        return m_resource->Write(helper);
    }
    else
    {
        return true;
    }
}

// Get/set modified flag
bool AxResourceInfo::GetModified() const
{
    if (m_resource)
        return m_resource->GetModified();
    else
        return false;
}

void AxResourceInfo::SetModified(bool mod)
{
    if (m_resource)
        m_resource->SetModified(mod);
}


#if 0
static int shPartSortFunc(AxResourcePart** part1, AxResourcePart** part2)
{
    if ((*part1)->m_position < (*part2)->m_position)
        return -1;
    else if ((*part1)->m_position > (*part2)->m_position)
        return 1;
    else
        return 0;
}

// Sort the parts according to the track position
void AxResourceInfo::SortParts()
{
    m_parts.Sort(shPartSortFunc);
}
#endif

/*
 * Bookshelf manager
 */

AxResourceLibrary* AxResourceLibrary::sm_library = NULL;

void AxResourceLibrary::Init()
{
    sm_library = this;

    m_modified = false;
    m_selection = -1;
    m_sortByCol = 0;
    m_disableProcessing = false;
    m_commandManager = NULL;

    m_summaryCoverSize = wxSize(120, 120);
    m_thumbnailCoverSize = wxSize(120, 120);

    m_lastIdentifier = 0;
    m_workingSet.SetResourceLibrary(this);
}

AxResourceLibrary::~AxResourceLibrary()
{
}

// Initialize modules etc.
bool AxResourceLibrary::Initialize()
{
    AxModule::RegisterModules();
    return AxModule::InitializeModules(this);
}

// Uninitialize modules etc.
bool AxResourceLibrary::Uninitialize()
{
    AxModule::CleanUpModules();
    return true;
}

void AxResourceLibrary::Copy(const AxResourceLibrary& library)
{
    m_resources = library.m_resources;
    m_settings = library.m_settings;
    m_workingSet = library.m_workingSet;
}

// Clear all
void AxResourceLibrary::Clear()
{
    m_resources.Clear();
    m_settings.GetSupportedFiles().Clear();
    m_settings.GetAdditionalLibraryPaths().Clear();
    m_workingSet.Clear();

    m_currentAuthors.Clear();
    m_currentPublishers.Clear();
    m_currentSubjects.Clear();
    m_currentFormats.Clear();
}

// Allocate new identifier
long AxResourceLibrary::AllocateNewIdentifier()
{
    m_lastIdentifier ++;
    return m_lastIdentifier;
}

bool AxResourceLibrary::Read(wxInputStream& stream, wxDataInputStream& dataStream, int flags)
{
    if ((flags & axREAD_MERGE) == 0)
    {
        m_resources.Clear();

        m_currentAuthors.Clear();
        m_currentPublishers.Clear();
        m_currentSubjects.Clear();
        m_currentFormats.Clear();
    }

    wxArrayInt deletions;

    wxDataStreamHelper helper(stream, dataStream);
    size_t i;

    int bookCount = 0;
    wxVariant attr;
    while (helper.Read(attr))
    {
        wxString name(attr.GetName());
        if (name == wxT("book-count"))
            bookCount = attr.GetLong();
#if 0
        else if (name == wxT("book-default-cover"))
        {
            m_coverDesign.Read(stream, dataStream);
        }
#endif
        else if (name == wxT("book-deletions"))
        {
            int count = attr.GetLong();
            for (i = 0; i < (size_t) count; i++)
            {
                int deletion = 0;
                dataStream >> deletion;
                deletions.Add(deletion);
            }
        }
    }

    m_lastIdentifier = 0;

    for (i = 0; i < (size_t) bookCount; i++)
    {
        AxResourceInfo book;
        book.Read(helper);

        if (book.m_recordIdentifier > m_lastIdentifier)
            m_lastIdentifier = book.m_recordIdentifier;

        bool added = false;
        if (flags & axREAD_MERGE)
        {
            AxResourceInfo* existingBook = FindByRecordIdentifier(book.m_recordIdentifier);
            if (existingBook)
            {
                *existingBook = book;
                // If we have to save at all, then we'll need to save this
                // one since it's part of the 'changed' set.
                existingBook->SetModified(true);
                added = true;

                AddCurrentInfo(*existingBook);
            }
        }

        if (!added)
        {
            AddResource(book, false /* not brand new */);
        }
    }

    if ((flags & axREAD_MERGE) && deletions.GetCount() > 0)
    {
        for (i = 0; i < (size_t) m_resources.GetCount(); i++)
        {
            if (deletions.Index((int) m_resources[i].m_recordIdentifier) != wxNOT_FOUND)
            {
                m_resources[i].m_deleted = true;
            }
        }
    }
    // m_siteDB.Read(stream, dataStream);

    return true;
}

bool AxResourceLibrary::Write(wxOutputStream& stream, wxDataOutputStream& dataStream, int flags)
{
    wxDataStreamHelper helper(stream, dataStream);
    size_t i;
    bool writeChangesOnly = (flags & axWRITE_CHANGES_ONLY) != 0;

    int bookCount = 0;
    for (i = 0; i < m_resources.GetCount(); i++)
    {
        if (!m_resources[i].m_deleted && (!writeChangesOnly || m_resources[i].GetModified()))
            bookCount ++;
    }

    helper.Write((int) bookCount, wxT("book-count"));

#if 0
    helper.Write(wxString(), wxT("book-default-cover"));
    m_coverDesign.Write(stream, dataStream);
#endif

    if (flags & axWRITE_CHANGES_ONLY)
    {
        wxArrayInt deletions;
        for (i = 0; i < m_resources.GetCount(); i++)
        {
            if (m_resources[i].m_deleted)
            {
                deletions.Add(m_resources[i].m_recordIdentifier);
            }
        }
        helper.Write((long)deletions.GetCount(), wxT("book-deletions"));
        for (i = 0; i < deletions.GetCount(); i++)
        {
            dataStream << deletions[i];
        }
    }

    helper.WriteEnd();

    for (i = 0; i < m_resources.GetCount(); i++)
    {
        AxResourceInfo& book = m_resources[i];
        if (!book.m_deleted && (!writeChangesOnly || m_resources[i].GetModified()))
            book.Write(helper);
    }

    // m_siteDB.Write(stream, dataStream);

    return true;
}

bool AxResourceLibrary::Read(const wxString& filename, int flags)
{
    wxFileInputStream stream(filename);
    if (!stream.Ok())
        return false;

    wxDataInputStream dataStream(stream);

    wxString appSymbol;
    double versionNumber;
    dataStream >> appSymbol;
    dataStream >> versionNumber;

    if (appSymbol != wxT("axresourcelibrary-utf8"))
    {
        wxLogError(_("Sorry, this is not a valid bookshelf."), _("Bookshelf"), wxOK|wxICON_EXCLAMATION);
        return false;
    }

    return Read(stream, dataStream, flags);
}

bool AxResourceLibrary::Write(const wxString& filename, int flags)
{
    wxFileOutputStream stream(filename);
    if (!stream.Ok())
        return false;

    wxDataOutputStream dataStream(stream);

    dataStream << wxString(wxT("axresourcelibrary-utf8"));
    dataStream << (double) axLIBRARY_FORMAT_VERSION;

    if (Write(stream, dataStream, flags))
    {
        SetModified(false);
        return true;
    }
    else
        return false;
}

// Decide whether we can save modifications seperately, or whether we should
// write out the whole file and delete the modifications file.
// TODO: save default cover design separately!! Or just save in changes file.
bool AxResourceLibrary::CanWriteChangesSeparately(int percentageThreshold)
{
    int modCount = 0;
    size_t i;
    for (i = 0; i < m_resources.GetCount(); i++)
    {
        if (m_resources[i].GetModified())
            modCount ++;
    }

    int percentageModified = (int) (100.0 * (double(modCount) / double(m_resources.GetCount())));
    return (percentageModified <= percentageThreshold);
}

// Create a working set from the currently loaded books
void AxResourceLibrary::CreateFullWorkingSet()
{
    CreateFullWorkingSet(m_workingSet);
}

void AxResourceLibrary::CreateFullWorkingSet(AxResourceSet& workingSet)
{
    workingSet.Clear();
    size_t i;
    for (i = 0; i < m_resources.GetCount(); i++)
    {
        if (!m_resources[i].m_deleted && m_resources[i].m_resource)
            workingSet.GetResources().Add(m_resources[i].m_resource);
    }
    
    Sort(0);
}

// Match 'matchText' against 'matchAgainst', optionally constraining to
// whole-word only.
bool AxResourceLibraryMatchString(const wxString& matchAgainst, const wxString& matchText, bool wholeWordOnly, int* position = NULL)
{
    // Fast operation if not matching against whole words only
    if (!wholeWordOnly)
    {
        int p = matchAgainst.Find(matchText);
        if (position)
            *position = p;
        return (p != -1);
    }

    wxString left(matchAgainst);
    bool success = false;
    int pos = 0;
    int matchTextLen = (int) matchText.Length();
    while (!success && !matchAgainst.IsEmpty())
    {
        pos = left.Find(matchText);
        if (pos == -1)
            return false;

        bool firstCharOK = false;
        bool lastCharOK = false;
        if (pos == 0 || !wxIsalnum(left[(size_t) (pos-1)]))
            firstCharOK = true;

        if (((pos + matchTextLen) == (int) left.Length()) || !wxIsalnum(left[(size_t) (pos + matchTextLen)]))
            lastCharOK = true;

        if (firstCharOK && lastCharOK)
            success = true;

        left = left.Mid(pos+1);
    }
    if (position)
        *position = pos;
    return success;
}

// Match 'matchText' against 'matchAgainst', optionally constraining to
// whole-word only.
bool AxResourceLibraryMatchString(const wxString& matchAgainst, const wxString& matchText, bool wholeWordOnly, bool matchCase, int* position = NULL)
{
    if (matchCase)
        return AxResourceLibraryMatchString(matchAgainst, matchText, wholeWordOnly, position);
    else
    {
        wxString str1(matchAgainst);
        wxString str2(matchText);
        str1.MakeLower();
        str2.MakeLower();

        return AxResourceLibraryMatchString(str1, str2, wholeWordOnly, position);
    }
}

// Match?
bool AxResourceLibrary::Match(AxResource& resource, const wxString& field, const wxString& value, int flags)
{
    bool wholeWordOnly = (flags & axSEARCH_EXACT_MATCH) != 0;
    bool matchCase = (flags & axSEARCH_IGNORE_CASE) == 0;

    if (field.IsEmpty())
    {
        size_t i;
        for (i = 0; i < resource.GetProperties().GetCount(); i++)
        {
            const wxString& val = resource.GetPropertyPtr(i)->GetValue();
            if (!val.IsEmpty() && AxResourceLibraryMatchString(val, value, wholeWordOnly, matchCase))
                return true;            
        }
        return false;
    }
    else
    {
        wxString val = resource.GetPropertyString(field);
        if (val.IsEmpty())
            return false;
        else
            return AxResourceLibraryMatchString(val, value, wholeWordOnly, matchCase);
    }

#if 0
    bool wholeWordOnly = (flags & axSEARCH_EXACT_MATCH) != 0;
    bool matchCase = (flags & axSEARCH_IGNORE_CASE) == 0;

    wxString ext;
    if (field == shBOOKSHELF_BY_SUBJECT || field.IsEmpty())
    {
        if (stMatchString(info.m_subject, value, wholeWordOnly, matchCase))
            return true;
    }
    if (field == shBOOKSHELF_BY_AUTHOR || field.IsEmpty())
    {
        if (stMatchString(info.m_author, value, wholeWordOnly, matchCase))
            return true;
    }
    if (field == shBOOKSHELF_BY_PUBLISHER || field.IsEmpty())
    {
        if (stMatchString(info.m_publisher, value, wholeWordOnly, matchCase))
            return true;
    }
    if (field == shBOOKSHELF_BY_TITLE || field.IsEmpty())
    {
        if (stMatchString(info.m_title, value, wholeWordOnly, matchCase))
            return true;
    }
#if 0
    if (field == shBOOKSHELF_BY_DESCRIPTION || field.IsEmpty())
    {
        if (stMatchString(info.m_description, value, wholeWordOnly, matchCase))
            return true;
    }
#endif
    if (field == shBOOKSHELF_BY_FORMAT || field.IsEmpty())
    {
        ext = info.GetFormat().Upper();
        if (stMatchString(ext, value, wholeWordOnly, matchCase))
            return true;
    }
    if (field == shBOOKSHELF_BY_STATUS || field.IsEmpty())
    {
        if (value == wxT("Owned") && info.m_bookStatus == AxResourceInfoOwned)
            return true;
        else if (value == wxT("Wanted") && info.m_bookStatus == AxResourceInfoWanted)
            return true;
    }
#endif
    return false;
}

// Create a working set from a specification. If field is empty,
// allow any field to match
void AxResourceLibrary::CreateWorkingSetFromSearch(const wxString& field, const wxString& value, int flags)
{
    CreateWorkingSetFromSearch(m_workingSet, field, value, flags);
}

void AxResourceLibrary::CreateWorkingSetFromSearch(AxResourceSet& workingSet, const wxString& field, const wxString& value, int flags)
{
    workingSet.Clear();
    size_t i;
    for (i = 0; i < m_resources.GetCount(); i++)
    {
        AxResourceInfo& info = m_resources[i];
        if (!info.m_deleted && info.m_resource && Match(* info.m_resource, field, value, flags))
            workingSet.GetResources().Add(m_resources[i].m_resource);
    }
    Sort(0);
}

// Apply search to current working set
void AxResourceLibrary::ApplySearchToWorkingSet(const wxString& field, const wxString& value, int flags)
{
    ApplySearchToWorkingSet(m_workingSet, value, field, flags);
}

// Apply search to current working set
void AxResourceLibrary::ApplySearchToWorkingSet(AxResourceSet& workingSet, const wxString& field, const wxString& value, int flags)
{
    size_t i = 0;
    while (i < m_workingSet.GetCount())
    {
        AxResource* resource = m_workingSet.GetResource(i);
        if (Match(* resource, field, value, flags))
            i ++;
        else
        {
            m_workingSet.GetResources().RemoveAt(i);
        }
    }
    Sort(0);
}

// Load books from a folder
bool AxResourceLibrary::LoadFromFolder(const wxString& folder, bool clearFirst, bool checkIfFileLoaded)
{
    if (clearFirst)
        Clear();

    size_t n = m_resources.GetCount();

    bool success = LoadFromFolder(m_resources, folder, clearFirst, checkIfFileLoaded, true);

    if (m_resources.GetCount() > n)
        SetModified(true);

    return success;
}

// Load books from a folder
bool AxResourceLibrary::LoadFromFolder(AxResourceInfoArray& books, const wxString& folder, bool clearFirst, bool checkIfFileLoaded, bool allocId)
{
    if (clearFirst)
        books.Clear();

    AxFolders folders;
    RecursivelyFindFiles(folder, folders);

    size_t i;
    for (i = 0; i < folders.GetCount(); i++)
    {
        wxArrayString filenames(folders[i].m_filenames);

        // This aggregates and removes any files that belong in one book.
        // Then the following loop can deal with the remaining files.
        AggregateFiles(books, filenames, checkIfFileLoaded, allocId);

        size_t j;
        for (j = 0; j < filenames.GetCount(); j++)
        {
            if (!checkIfFileLoaded || (FindByFile(books, filenames[j]) == -1))
            {
                wxString filename = filenames[j];
                AxResource* resource = CreateResourceFromFile(filename);
                if (resource)
                    AddResource(resource);
            }
        }
    }

    return true;
}

// Load resource from file
AxResource* AxResourceLibrary::CreateResourceFromFile(const wxString& filename)
{
    AxModuleList::Node *node;
    for ( node = AxModule::GetModules().GetFirst(); node; node = node->GetNext() )
    {
        if (node->GetData()->IsFileSupported(filename))
        {
            AxResource* resource = node->GetData()->LoadResource(filename);
            if (resource)
                return resource;
        }
    }
    return NULL;
}

#if 0
// Load book info
bool AxResourceLibrary::LoadBook(AxResourceInfo& info, const wxString& filename, bool makeThumbnail, const wxSize& thumbnailImageSize, bool& foundCoverImage)
{
    wxUnusedVar(info);
    wxUnusedVar(filename);
    wxUnusedVar(makeThumbnail);
    wxUnusedVar(thumbnailImageSize);
    wxUnusedVar(foundCoverImage);

#if 0
    info.SetFilename(filename);

    wxString ext = AxFileUtilities::GetExtension(filename).Lower();
    if (ext == wxT("inf"))
    {
        info.m_parts.Clear();

        info.LoadConfig(filename);
    }
    else if (ext == wxT("epub"))
    {
        LoadEpubBookInfo(filename, info, true, thumbnailImageSize, foundCoverImage);
    }
    else if (ext == wxT("mp3") && info.m_parts.GetCount() < 2)
    {
        wxMP3Info mp3Info(filename);

        wxString album, trackName;
        wxString title;

        if (!mp3Info.GetTitle().IsEmpty())
            trackName = mp3Info.GetTitle();
        if (!mp3Info.GetAlbum().IsEmpty())
            album = mp3Info.GetAlbum();

        if (!album.IsEmpty() && !trackName.IsEmpty())
            title = album + wxT(" - ") + trackName;
        else if (!album.IsEmpty())
            title = album;
        else
            title = trackName;

        info.m_title = title;
        if (!mp3Info.GetArtist().IsEmpty())
            info.m_author = mp3Info.GetArtist();
        if (!mp3Info.GetComment().IsEmpty())
            info.m_description = mp3Info.GetComment();
        if (!mp3Info.GetYear().IsEmpty())
            info.m_date = mp3Info.GetYear();

        AxResourcePart part;
        part = mp3Info;

        info.m_parts.Clear();
        info.m_parts.Add(part);
    }
    
    if (info.m_title.IsEmpty())
    {
        info.m_title = wxFileNameFromPath(filename);
        AxFileUtilities::StripExtension(info.m_title);
    }
    
    if (info.m_publisher.IsEmpty())
        info.m_publisher = _("Unknown");
    
    if (info.m_author.IsEmpty())
        info.m_author = _("Unknown");
    
    if (info.m_subject.IsEmpty())
        info.m_subject = _("Unknown");

    if (!info.m_thumbnailImageBlock.Ok())
    {
        LoadImageFromCoverDesign(info, thumbnailImageSize);
    }
#endif    
    return true;
}
#endif

// Aggregate any files that belong together and add to books
bool AxResourceLibrary::AggregateFiles(AxResourceInfoArray& books, wxArrayString& files, bool checkIfFileLoaded, bool allocId)
{
    files.Sort();

    AxModuleList::Node *node;
    for ( node = AxModule::GetModules().GetFirst(); node; node = node->GetNext() )
    {
        if (files.GetCount() == 0)
            break;
        else
            node->GetData()->AggregateFiles(this, files, checkIfFileLoaded);
    }

    return true;
}

// Load image from cover book or default design
bool AxResourceLibrary::LoadImageFromCoverDesign(AxResourceInfo& info, const wxSize& thumbnailImageSize)
{
#if 0
    if (!info.m_thumbnailImageBlock.Ok())
    {
        cdKeywordProvider keywordProvider;
        InstantiateKeywords(info, keywordProvider);
        cdCoverDesign* coverDesign = NULL;
        if (info.m_coverDesign.IsOk())
            coverDesign = & info.m_coverDesign;
        else if (GetDefaultCoverDesign().IsOk())
            coverDesign = & GetDefaultCoverDesign();

        if (coverDesign)
        {
            bool outlineMode = coverDesign->GetOutlineMode();

            coverDesign->SetOutlineMode(false);
            wxBitmap bitmap = coverDesign->CreateDesign(& keywordProvider, thumbnailImageSize);
            coverDesign->SetOutlineMode(outlineMode);

            wxImage image(bitmap.ConvertToImage());
            return info.m_thumbnailImageBlock.MakeImageBlock(image);            
        }
    }
#endif
    return false;
}

// Load information from epub file
bool AxResourceLibrary::LoadEpubBookInfo(const wxString& filename, AxResourceInfo& info, bool makeThumbnail, const wxSize& thumbnailImageSize, bool& foundCoverImage)
{
#if 0
    ebEpubParser parser;
    ebBasicInfo basicInfo;
    wxString coverImageZipFilename;
    if (parser.ReadBasicInfo(filename, basicInfo, coverImageZipFilename))
    {
        info = basicInfo;
        
        if (!coverImageZipFilename.IsEmpty())
        {
            wxImage image;
            wxImageBlock imageBlock;
            if (parser.CreateImageBlockFromZipImage(filename, coverImageZipFilename, imageBlock, image) && image.IsOk())
            {
                int maxWidth = thumbnailImageSize.x;
                int maxHeight = thumbnailImageSize.y;
                
                double scaleX = ((double) maxWidth)/((double) image.GetWidth());
                double scaleY = ((double) maxHeight)/((double) image.GetHeight());
                
                if (scaleX < 1.0 || scaleY < 1.0)
                {
                    double scale = wxMin(scaleX, scaleY);
                    int newWidth = (int) (scale * image.GetWidth());
                    int newHeight = (int) (scale * image.GetHeight());
                    image.Rescale(newWidth, newHeight, wxIMAGE_QUALITY_HIGH);
                }
                info.m_thumbnailImageBlock.MakeImageBlock(image);

                foundCoverImage = true;
            }
        }

        return true;
    }
    else
#endif
    {
        return false;
    }
}

// Remove books from library
bool AxResourceLibrary::RemoveSelectedBooks(bool interactive, wxWindow* win)
{
    bool anyRemoved = false;
    wxArrayInt selections;
    if (GetSelections(selections) && selections.GetCount() > 0)
    {
        size_t i;
        for (i = 0; i < selections.GetCount(); i++)
        {
            if (RemoveResource(selections[i], interactive, win))
                anyRemoved = true;
        }
    }

    if (anyRemoved)
    {
        //CreateWorkingSetFromCurrentSettings();
        //PopulateListAndCoverControls();
        //UpdateSelectedBookInfo(m_selection);
    }

    return true;
}

// Remove resource from library
// TODO: make it accessible
bool AxResourceLibrary::RemoveResource(int sel, bool interactive, wxWindow* win)
{
    AxResourceInfo& info = m_resources[sel];

    bool removeIt = true;
    if (interactive)
    {
        if (!win)
            win = wxTheApp->GetTopWindow();
        wxString title(wxT("Unknown"));
        if (info.m_resource && !info.m_resource->GetPropertyString(axPROPERTY_TITLE).IsEmpty())
            title = info.m_resource->GetPropertyString(axPROPERTY_TITLE);

        int ans = wxMessageBox(wxString::Format(_("Remove \"%s\" from the library?\n\nThis will not delete the file from disk."), title.c_str()),
                        _("Remove Resource"), wxYES_NO|wxICON_QUESTION, win);
        if (ans == wxNO)
            removeIt = false;
    }

    if (removeIt)
    {
        info.m_deleted = true;
        info.SetModified(true);
        SetModified(true);
        if (sel == m_selection)
            m_selection = -1;
        return true;
    }

    return false;
}

// Is this file in the index?
bool AxResourceLibrary::IsFileInIndex(const wxString& filename) const
{
    return FindByFile(filename) != -1;
}

bool AxResourceLibrary::IsFileInIndex(const AxResourceInfoArray& books, const wxString& filename) const
{
    return FindByFile(books, filename) != -1;
}

// Find by file
int AxResourceLibrary::FindByFile(const wxString& filename) const
{
    return FindByFile(m_resources, filename);
}

// Find by file
int AxResourceLibrary::FindByFile(const AxResourceInfoArray& books, const wxString& filename) const
{
    if (filename.IsEmpty())
        return -1;

    wxString f1(filename);

    // TODO: more efficient way, using hash table
    size_t i;
    for (i = 0; i < books.GetCount(); i++)
    {
        if (!books[i].m_deleted && books[i].m_resource)
        {
            if (books[i].m_resource->FindByFilename(filename))
                return i;
        }
    }

    return -1;
}

// Find by unique record identifier
AxResourceInfo* AxResourceLibrary::FindByRecordIdentifier(long id) const
{
    size_t i;
    for (i = 0; i < m_resources.GetCount(); i++)
    {
        if (m_resources[i].m_recordIdentifier == id)
            return & m_resources[i];
    }
    return NULL;
}


// Load books from a set of folders
bool AxResourceLibrary::LoadFromFolders(const wxArrayString& folders, bool clearFirst, bool checkIfFileLoaded)
{
    if (clearFirst)
        Clear();

    size_t i;
    for (i = 0; i < folders.GetCount(); i++)
    {
        LoadFromFolder(folders[i], false, checkIfFileLoaded);
    }

    return true;
}

// Recursively find all supported files
bool AxResourceLibrary::RecursivelyFindFiles(const wxString& folder, AxFolders& folders)
{
    {
        AxFolder axfolder;

        axfolder.m_folderName = folder;

        wxArrayString tempFilenames;
    
        wxDir dir;
        dir.GetAllFiles(folder, & tempFilenames, wxT("*.*"), wxDIR_FILES);

        // First check for our resource meta-information; if we found it, stop recursing
        // and return that file only, since we have a folder containing information for one book.

        size_t i;
        for (i = 0; i < tempFilenames.GetCount(); i++)
        {
            wxString f(tempFilenames[i]);
            wxString name(wxFileNameFromPath(f));
            if (name == wxT("ResourceMetaData.inf"))
            {
                axfolder.m_filenames.Add(f);
                folders.Add(axfolder);
                return true;
            }
        }

        for (i = 0; i < tempFilenames.GetCount(); i++)
        {
            wxString f(tempFilenames[i]);

            // TODO: remove this code if not needed
            if (!wxIsAbsolutePath(f))
                f = AxFileUtilities::AppendPaths(folder, f);

            if (IsFileSupported(f))
                axfolder.m_filenames.Add(f);
        }

        if (axfolder.m_filenames.GetCount() > 0)
            folders.Add(axfolder);
    }

    wxArrayString paths;

    wxDir dir(folder);
    if (dir.IsOpened())
    {
        wxString filename;
        
        bool cont = dir.GetFirst(&filename, wxT("*.*"), wxDIR_DIRS);
        while ( cont )
        {
            paths.Add(AxFileUtilities::AppendPaths(folder, filename));
            
            cont = dir.GetNext(&filename);
        }
    }

    size_t i;
    for (i = 0; i < paths.GetCount(); i++)
    {
        RecursivelyFindFiles(paths[i], folders);
    }

    return true;
}

// Purge files that no longer exist
// TODO: need to get user feedback for this.
bool AxResourceLibrary::PurgeDeletedFiles()
{
    bool anyDeleted = false;

    size_t i = 0;
    while (i < m_resources.GetCount())
    {
        if (m_resources[i].GetResource() && !m_resources[i].m_deleted && m_resources[i].GetResource()->GetLocalFile() && !m_resources[i].GetResource()->GetFilename().IsEmpty())
        {
            wxString filename = m_resources[i].GetResource()->GetFilename();
            if (!wxFileExists(filename))
            {
                m_resources[i].m_deleted = true;
                m_resources[i].GetResource()->SetModified(true);
                anyDeleted = true;
            }
        }
        i ++;
    }
    if (anyDeleted)
        SetModified(true);

    return anyDeleted;
}

// Is this file supported?
bool AxResourceLibrary::IsFileSupported(const wxString& filename) const
{
    AxModuleList::Node *node;
    for ( node = AxModule::GetModules().GetFirst(); node; node = node->GetNext() )
    {
        if (node->GetData()->IsFileSupported(filename))
            return true;
    }

    return false;
}

// Write HTML summary for this book
wxString AxResourceLibrary::CreateHtmlSummary(AxResourceInfo& info, const wxColour& backgroundColour, const wxColour& textColour)
{
    wxString html;
#if 0
    html << wxT("<html>\n<head>\n");
    html << wxT("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
    html << wxT("<title>Summary</title>\n</head>\n");

    wxString backgroundColourStr = backgroundColour.GetAsString(wxC2S_HTML_SYNTAX);
    wxString textColourStr = textColour.GetAsString(wxC2S_HTML_SYNTAX);

    html << wxT("<body bgcolor=\"") << backgroundColourStr << wxT("\" text=\"") << textColourStr << wxT("\">\n");

    html << wxT("<b><font size=+0>") << info.m_title << wxT("</font></b><p>\n");

    if (!info.m_author.IsEmpty())
    {
        html << wxT("<font size=-1>Author: <b>") << info.m_author << wxT("</b></font><br>\n");
    }

    if (!info.GetFormat().IsEmpty())
        html << wxT("<font size=-1>Format: <b>") << info.GetFormat() << wxT("</b></font><br>\n");

    if (!info.m_publisher.IsEmpty())
    {
        html << wxT("<font size=-1>Publisher: <b>") << info.m_publisher << wxT("</b></font><p>\n");
    }

    if (!info.m_description.IsEmpty())
    {
        html << wxT("<font size=-1>") << info.m_description << wxT("</font>\n");
    }

    html << wxT("</body>\n");
#endif
    return html;
}

// Write HTML blank page
wxString AxResourceLibrary::CreateHtmlBlankSummary(const wxColour& backgroundColour, const wxColour& textColour)
{
    wxString html;
    html << wxT("<html>\n<head>\n");
    html << wxT("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
    html << wxT("<title>Summary</title>\n</head>\n");

    wxString backgroundColourStr = backgroundColour.GetAsString(wxC2S_HTML_SYNTAX);
    wxString textColourStr = backgroundColour.GetAsString(wxC2S_HTML_SYNTAX);

    html << wxT("<body bgcolor=\"") << backgroundColourStr << wxT("\" text=\"") << textColourStr << wxT("\">\n");

    html << wxT("</body>\n");

    return html;
}

int AxResourceLibrary::CompareItemsHelper(AxResource& item1, AxResource& item2, int col)
{
    wxString string1;
    wxString string2;

#if 0
    if (col == 0)
    {
        string1 = item1.m_title;
        string2 = item2.m_title;
    }
    else if (col == 1)
    {
        string1 = item1.m_author;
        string2 = item2.m_author;
    }
    else if (col == 2)
    {
        string1 = item1.m_publisher;
        string2 = item2.m_publisher;
    }
    else if (col == 3)
    {
        string1 = item1.m_subject;
        string2 = item2.m_subject;
    }
    else if (col == 4)
    {
        string1 = item1.GetFormat();
        string2 = item2.GetFormat();
    }
#endif
    return string1.CmpNoCase(string2);
}

// Compare two items for sorting purposes
int AxResourceLibrary::CompareItems(AxResource& item1, AxResource& item2, int col)
{
    int ans = CompareItemsHelper(item1, item2, col);

    if (ans != 0)
        return ans;

    // Sort by other columns too
    int i;
    for (i = 0; i < 5; i++)
    {
        if (i != col)
        {
            ans = CompareItemsHelper(item1, item2, i);
            if (ans != 0)
                return ans;
        }
    }

    return 0;
}

static int AxCompareFunc(AxResource** a, AxResource** b)
{
    return AxResourceLibrary::GetResourceLibrary()->CompareItems(* (*a), * (*b), AxResourceLibrary::GetResourceLibrary()->GetSortByCol());
}

// Sort the working set
void AxResourceLibrary::Sort(int col)
{
    SetSortByCol(col);
    m_workingSet.GetResources().Sort(AxCompareFunc);
}

#if 0
// Get selected item (index into the array of books)
int AxResourceLibrary::GetSelection() const
{
    long i = wxListCtrlGetSelection(* m_listCtrl);
    if (i != -1)
    {
        int idx = m_listCtrl->GetItemData(i);
        return idx;
    }
    else
        return -1;
}
#endif

#if 0
// Set selection - pass the book index
void AxResourceLibrary::SetSingleSelection(int sel, bool updateListView, bool updateCoverView)
{
    ClearSelections();
    SetSelection(sel, updateListView, updateCoverView);
}
#endif

// Clear selections
void AxResourceLibrary::ClearSelections()
{
    wxArrayInt selections;
    if (GetSelections(selections))
    {
        size_t i;
        for (i = 0; i < selections.GetCount(); i++)
        {
            SetSelection(selections[i], false);
        }
    }
}

// Set selection - pass the book index
void AxResourceLibrary::SetSelection(int sel, bool select)
{
    DisableProcessing(true);
    
    if (select)
    {
        m_selection = sel;
    }
    else
    {
    }
    
    DisableProcessing(false);
}

// Get selected item (index into the array of books)
AxResourceInfo* AxResourceLibrary::GetSelectedBook() const
{
    if (m_selection != -1 && m_selection < (int) m_resources.GetCount())
        return & m_resources[m_selection];
    else
        return NULL;
}

// Get all selections
bool AxResourceLibrary::GetSelections(wxArrayInt& selections)
{
#if 0
    long i = 0;
    for (i = 0; i < m_listCtrl->GetItemCount(); i++)
    {
        long state = m_listCtrl->GetItemState(i, wxLIST_STATE_SELECTED) ;
        if (state & wxLIST_STATE_SELECTED)
        {
            long dataIdx = m_listCtrl->GetItemData(i);
            selections.Add(dataIdx);

        }
    }
#endif
    return true;
}

// Add to current keyword arrays
void AxResourceLibrary::AddToCurrentAuthors(const wxString& author)
{
    if (m_currentAuthors.Index(author) == wxNOT_FOUND)
        m_currentAuthors.Add(author);
}

void AxResourceLibrary::AddToCurrentPublishers(const wxString& publisher)
{
    if (m_currentPublishers.Index(publisher) == wxNOT_FOUND)
        m_currentPublishers.Add(publisher);
}

void AxResourceLibrary::AddToCurrentSubjects(const wxString& subject)
{
    if (m_currentSubjects.Index(subject) == wxNOT_FOUND)
        m_currentSubjects.Add(subject);
}

void AxResourceLibrary::AddToCurrentFormats(const wxString& format)
{
    if (m_currentFormats.Index(format) == wxNOT_FOUND)
        m_currentFormats.Add(format);
}

// Add to current author etc. arrays
void AxResourceLibrary::AddCurrentInfo(const AxResourceInfo& info)
{
#if 0
    AddToCurrentAuthors(info.m_author);
    AddToCurrentPublishers(info.m_publisher);
    AddToCurrentSubjects(info.m_subject);
    AddToCurrentFormats(info.GetFormat());
#endif
}

int AxResourceLibrary::AddResource(AxResource* resource)
{
    AxResourceInfo info;
    info.SetResource(resource);

    return AddResource(info, true);
}

int AxResourceLibrary::AddResource(AxResourceInfo& info, bool isNew)
{
    if (isNew)
    {
        info.m_recordIdentifier = AllocateNewIdentifier();
        info.SetModified(true);
    }

    int idx = m_resources.GetCount();

    m_resources.Add(info);
    m_resources[idx].SetOwnsResource(true);

    AddCurrentInfo(info);

    return idx;
}


// Perform a search
bool AxResourceLibrary::DoSearch(const wxString& WXUNUSED(searchText))
{
    // CreateWorkingSetFromCurrentSettings();
    // PopulateListAndCoverControls();

    return true;
}

// Update main panel according to current document
void AxResourceLibrary::UpdateSelectedBookInfo(int idx, bool forceClear)
{
    wxUnusedVar(idx);
    wxUnusedVar(forceClear);
}

// Upload files
bool AxResourceLibrary::UploadFiles(wxWindow* win)
{
    wxUnusedVar(win);
#if 0
    wxArrayInt selections;
    GetSelections(selections);
    if (selections.GetCount() > 0)
    {
        wxString msg = _("Choose the destination folder");
        wxWindow* parent = wxGetTopLevelParent(win);
        
        wxString originalPath; // = wxGetApp().GetSettings().m_uploadPath;
        
        wxDirDialog dirDlg(parent, msg, originalPath, wxDD_DEFAULT_STYLE);
        if (dirDlg.ShowModal() == wxID_OK)
        {
            wxString path = dirDlg.GetPath();
            wxGetApp().GetSettings().m_uploadPath = path;

            wxArrayString sourcePaths, sourceLabels;
            size_t i;
            for (i = 0; i < selections.GetCount(); i++)
            {
                AxResourceInfo& info = m_resources[selections[i]];
                size_t j;
                for (j = 0; j < info.m_parts.GetCount(); j++)
                {
                    wxString srcFile = info.m_parts[j].m_filename;
                    sourcePaths.Add(srcFile);

                    wxString label(info.m_title);
                    wxString fileTitle = info.m_parts[j].GetTitle();
                    if (!fileTitle.IsEmpty())
                        label = label + wxT(" - ") + fileTitle;
                    sourceLabels.Add(label);
                }
            }

            if (sourcePaths.GetCount() == 0)
            {
                wxMessageBox(_("No files to copy."), _("File Copy"), wxICON_INFORMATION|wxOK, parent);
                return false;
            }

            wxArrayInt selections;

            if (sourcePaths.GetCount() > 1)
            {
                wxMultipleChoiceDialog choiceDialog(parent, _("Check the files you wish to copy."), _("Upload Files"), sourceLabels);
                choiceDialog.SelectAll();

                if (choiceDialog.ShowModal() == wxID_OK)
                {
                    selections = choiceDialog.GetSelections();
                    if (selections.GetCount() == 0)
                        return false;
                }
                else
                    return false;
            }

            int filesCopied = 0;
            wxString firstFile(wxFileNameFromPath(sourcePaths[selections[0]]));
            wxString initialMsg = wxString::Format(_("Copying %s..."), firstFile.c_str());

            wxProgressDialog progressDialog(_("Uploading Files"), initialMsg, selections.GetCount(), parent,
                wxPD_AUTO_HIDE|wxPD_APP_MODAL|wxPD_CAN_ABORT|wxPD_ELAPSED_TIME|wxPD_ESTIMATED_TIME|wxPD_REMAINING_TIME);
            
            wxBusyCursor busy;
            for (i = 0; i < selections.GetCount(); i++)
            {
                wxString srcFile(sourcePaths[selections[i]]);
                wxString destFile = AxFileUtilities::AppendPaths(path, wxFileNameFromPath(srcFile));
                wxString srcFilename(wxFileNameFromPath(sourcePaths[selections[i]]));

                if (!progressDialog.Update(i, wxString::Format(_("Copying %s..."), srcFilename.c_str())))
                {
                    break;
                }

                if (!wxFileExists(srcFile) || !wxCopyFile(srcFile, destFile))
                {
                    wxString msg(wxString::Format(_("%s could not copy from %s to %s."), srcFile.c_str(), destFile.c_str()));
                    
                    wxMessageBox(msg, _("File Copy Problem"), wxICON_INFORMATION|wxOK, parent);
                    return false;
                }
                else
                    filesCopied ++;
            }

            //wxMessageBox(wxString::Format(_("Copied %d file(s) to %s."), filesCopied, path.c_str()), _("Upload Complete"), wxICON_INFORMATION|wxOK, parent);
            return true;
        }
    }
#endif
    return false;
}

// Can upload files?
bool AxResourceLibrary::CanUploadFiles() const
{
    // return (m_listCtrl && m_listCtrl->GetSelection() != -1;
    return m_selection != -1;
}

// Refresh the display using current settings
// TODO notify objects that have expressed interest - could refresh current
// view.
bool AxResourceLibrary::RefreshAll()
{
    return true;
}

#if 0
// Instantiate the keyword provider
void AxResourceLibrary::InstantiateKeywords(AxResourceInfo& info, cdKeywordProvider& keywordProvider)
{
    keywordProvider.AddKeyword(wxT("%TITLE%"),        & info.m_title);
    keywordProvider.AddKeyword(wxT("%ID%"),           & info.m_id);
    keywordProvider.AddKeyword(wxT("%LANGUAGE%"),     & info.m_language);
    keywordProvider.AddKeyword(wxT("%AUTHOR%"),       & info.m_author);
    keywordProvider.AddKeyword(wxT("%CONTRIBUTORS%"), & info.m_contributors);
    keywordProvider.AddKeyword(wxT("%PUBLISHER%"),    & info.m_publisher);
    keywordProvider.AddKeyword(wxT("%URL%"),          & info.m_url);
    keywordProvider.AddKeyword(wxT("%SUBJECT%"),      & info.m_subject);
    keywordProvider.AddKeyword(wxT("%DESCRIPTION%"),  & info.m_description);
    keywordProvider.AddKeyword(wxT("%DATE%"),         & info.m_date);
    keywordProvider.AddKeyword(wxT("%RIGHTS%"),       & info.m_rights);
    keywordProvider.AddKeyword(wxT("%COVERAGE%"),     & info.m_coverage);
    keywordProvider.AddKeyword(wxT("%TYPE%"),         & info.m_type);
    keywordProvider.AddKeyword(wxT("%FORMAT%"),       & info.m_format);
    keywordProvider.AddKeyword(wxT("%SOURCE%"),       & info.m_source);
    keywordProvider.AddKeyword(wxT("%RELATION%"),     & info.m_relation);
}
#endif

#if 0
// Create an array of books representing matches returned from
// isbndb.com
// Use e.g. http://isbndb.com/api/books.xml?access_key=NKH3E8CJ&index1=title&value1=inkheart&results=texts
bool AxResourceLibrary::ParseBooksISBNDB(const wxString& filename, AxResourceInfoArray& books, int& totalResults, int& pageSize, int& thisPage, int& numberShown,
                                          const wxString& title)
{
    if (!wxFileExists(filename))
        return false;

    wxFileInputStream stream(filename);
    
    wxXmlDocument* xmlDoc = new wxXmlDocument;
    if (xmlDoc->Load(stream))
    {
        wxXmlNode* rootNode = xmlDoc->GetRoot();
        if (rootNode)
        {
            wxXmlNode* listNode = GetParamNode(rootNode, wxT("BookList"));
            if (listNode)
            {
                totalResults = wxAtoi(listNode->GetPropVal(wxT("total_results"), wxEmptyString));
                pageSize = wxAtoi(listNode->GetPropVal(wxT("page_size"), wxEmptyString));
                thisPage = wxAtoi(listNode->GetPropVal(wxT("page_number"), wxEmptyString));
                numberShown = wxAtoi(listNode->GetPropVal(wxT("shown_results"), wxEmptyString));
                
                wxXmlNode* dataChild = listNode->GetChildren();
                while (dataChild)
                {
                    if (dataChild->GetName() == wxT("BookData"))
                    {
                        AxResourceInfo info;
                        info.m_title = GetText(dataChild, wxT("Title"));
                        wxString bookTitleLong = GetText(dataChild, wxT("TitleLong"));
                        if (!bookTitleLong.IsEmpty())
                            info.m_titleLong = bookTitleLong;
                        info.m_id = dataChild->GetPropVal(wxT("isbn"), wxEmptyString);

                        info.m_author = GetText(dataChild, wxT("AuthorsText"));
                        info.m_author.Trim(true);
                        info.m_author.Trim(false);

                        if (!info.m_author.IsEmpty() && info.m_author.Last() == wxT(','))
                        {
                            info.m_author = info.m_author.Mid(0, info.m_author.Length()-1);
                        }

                        info.m_publisher = GetText(dataChild, wxT("PublisherText"));
                        info.m_description = GetText(dataChild, wxT("Summary"));
                        info.m_notes = GetText(dataChild, wxT("Notes"));

                        bool canAdd = true;
                        if (!title.IsEmpty())
                        {
                            if (info.m_title.Lower() != title.Lower())
                                canAdd = false;
                        }

                        if (axFindByISBN(books, info.m_id) != -1)
                            canAdd = false;

                        if (canAdd)
                            books.Add(info);
                        
                    }
                    dataChild = dataChild->GetNext();
                }
            }
        }
    }
    delete xmlDoc;
    return true;
}

// Create an array of books representing matches returned from
// isbndb.com
bool AxResourceLibrary::DownloadAndParseBooksISBNDB(const wxString& htmlQuery, AxResourceInfoArray& books, int& totalResults, int& pageSize, int& thisPage, int& numberShown,
                                                     const wxString& title)
{
    wxString destFilename = wxGetTempFileName(wxT("web"));
    bool success = false;

    if (DownloadFileQuietly(htmlQuery, destFilename, GetMainFrame()) && wxFileName(destFilename).GetSize() != 0)
    {
        success = ParseBooksISBNDB(destFilename, books, totalResults, pageSize, thisPage, numberShown, title);
    }
    wxRemoveFile(destFilename);

    return success;
}

// Query ISBNDB.com, doing several queries at once.
bool AxResourceLibrary::QueryISBNDB(AxResourceInfoArray& books, const wxArrayString& queries, const wxString& index, const wxString& developerId, wxWindow* parentWindow, int flags)
{
    parentWindow->Enable(false);

    bool success = true;

    wxBusyInfo busyInfo(_("Querying ISBNDB.com..."), parentWindow);
    size_t i;
    for (i = 0; i < queries.GetCount(); i++)
    {
        wxString queryValue = queries[i];
        wxString title;
        if (flags & axISBN_QUERY_EXACT_MATCH)
            title = queryValue;

        wxString currentQuery = wxT("http://isbndb.com/api/books.xml?access_key=") + developerId +
            wxT("&index1=") + index + wxT("&value1=") + queryValue + wxT("&results=texts");
        currentQuery.Replace(wxT(" "), wxT("+"));

        int totalResults = 0, pageSize = 0, thisPage = 0, numberShown = 0;
        int resultsLeft = 0;
        int currentPage = 1;

        if (DownloadAndParseBooksISBNDB(currentQuery, books, totalResults, pageSize, thisPage, numberShown, title))
        {
            resultsLeft = totalResults - numberShown;
            while (resultsLeft > 0)
            {
                currentPage ++;
                wxString currentPageStr(wxString::Format(wxT("%d"), currentPage));

                currentQuery = wxT("http://isbndb.com/api/books.xml?access_key=") + developerId +
                    wxT("&index1=") + index + wxT("&value1=") + queryValue + wxT("&results=texts") + wxT("&page_number=") + currentPageStr;
                currentQuery.Replace(wxT(" "), wxT("+"));

                if (!DownloadAndParseBooksISBNDB(currentQuery, books, totalResults, pageSize, thisPage, numberShown, title) || numberShown == 0)
                    break;

                resultsLeft = totalResults - numberShown;

            }

            success = true;
        }
        else
            success = false;
    }

    parentWindow->Enable(true);

    return success;
}

// Downloads a given URL to a local file asychronously, showing a dialog.
// The function returns only when the file has downloaded or an error happened.
bool AxResourceLibrary::DownloadFileQuietly(const wxString& url, const wxString& destination, wxWindow* parentWindow, const wxString& msg)
{
    if (!parentWindow)
        parentWindow = wxTheApp->GetTopWindow();

    wxFileOutputStream stream(destination);
    wxDownloadDialog dlg;
    if (!dlg.Create(url, &stream, msg, wxEmptyString,
        parentWindow))
        return false;

    if (dlg.FakeShowModal() == wxDRF_FAILED)
    {
        // wxLogError(_("Sorry, could not download '%s'."), url.c_str());
        return false;
    }

    if (dlg.GetReturnCode() == wxDRF_USER_ABORTED)
        return false;

    return true;
}

// Find index of book by ISBN
int axFindByISBN(const AxResourceInfoArray& books, const wxString& isbn)
{
    if (isbn.IsEmpty())
        return -1;

    size_t i;
    for (i = 0; i < books.GetCount(); i++)
    {
        if (books[i].m_id == isbn)
            return i;
    }
    return -1;
}

#endif

// Refresh the library
bool AxResourceLibrary::RefreshLibrary(bool clearAll, bool interactive, wxWindow* parent)
{
    if (!parent)
        parent = wxTheApp->GetTopWindow();

    bool ok = true;
    wxString msg;
    if (GetSettings().GetMainLibraryPath().IsEmpty())
    {
        msg = _("Please choose a folder");
        ok = false;
    }
    else if (!wxDirExists(GetSettings().GetMainLibraryPath()))
    {
        msg = _("Please choose a folder");
        ok = false;
    }

    if (!ok)
    {
        if (interactive)
        {
            wxDirDialog dlg(parent, msg, wxEmptyString);
            if (dlg.ShowModal() == wxID_OK)
            {
                wxString path = dlg.GetPath();
                if (GetSettings().GetMainLibraryPath().IsEmpty() || !wxDirExists(GetSettings().GetMainLibraryPath()))
                    return false;

                GetSettings().SetMainLibraryPath(path);
                ok = true;
            }
            else
                return false;
        }
        else
            return false;
    }

    wxArrayString allFolders = GetSettings().GetAdditionalLibraryPaths();
    allFolders.Add(GetSettings().GetMainLibraryPath());

    bool checkIfLoaded = true;

    if (clearAll || m_resources.GetCount() == 0)
        checkIfLoaded = false;

    wxBusyCursor busy;

    LoadFromFolders(allFolders, clearAll, checkIfLoaded);
    PurgeDeletedFiles();

    RefreshAll();

    SaveLibrary();

    return true;
}

// Load the library
bool AxResourceLibrary::LoadLibrary()
{
    wxString filename = GetSettings().GetLibraryDataFile();
    wxString filenameChanges = GetSettings().GetLibraryChangesDataFile();
    if (!filename.IsEmpty() && wxFileExists(filename))
    {
        Read(filename);

        // Merge modifications file, if this file exists
        if (wxFileExists(filenameChanges))
        {
            Read(filenameChanges, axREAD_MERGE);
        }
    }

    RefreshAll();

#if 0
    if (!m_bookshelfManager.GetDefaultCoverDesign().IsOk())
    {
        wxString defaultCoverTemplate = AnthemionFileUtilities::AppendPaths(appDataDir, wxT("Templates/Simple 01.dtempl"));
        if (wxFileExists(defaultCoverTemplate))
        {
            m_bookshelfManager.GetDefaultCoverDesign().Read(defaultCoverTemplate);
        }
    }
#endif
    return true;
}

static void AxCopyTemporaryFile(const wxString& filename, const wxString& tmpFilename)
{
    if (wxFileExists(filename))
    {
        wxString backupFilename = filename + wxT(".bak");
        if (wxFileExists(backupFilename))
            wxRemoveFile(backupFilename);
        wxCopyFile(filename, backupFilename);
        wxRemoveFile(filename);
    }
    
    if (!wxRenameFile(tmpFilename, filename))
    {
        wxCopyFile(tmpFilename, filename);
        wxRemove(tmpFilename);
    }
}

// Save the library
bool AxResourceLibrary::SaveLibrary(bool forceConsolidatedWrite)
{
    if (GetModified())
    {
        wxString filename;
        wxString tmpFilename;
        int flags = 0;
        bool removeChangesFile = false;
        wxString changesFilename = GetSettings().GetLibraryChangesDataFile();

        if (CanWriteChangesSeparately() && !forceConsolidatedWrite)
        {
            filename = changesFilename;
            tmpFilename = filename + wxT(".tmp");
            flags = axWRITE_CHANGES_ONLY;
        }
        else
        {
            // We'll write everything, so no need to changes changes.dat.
            filename = GetSettings().GetLibraryDataFile();
            tmpFilename = filename + wxT(".tmp");
            removeChangesFile = true;
        }

        if (Write(tmpFilename, flags))
        {
            AxCopyTemporaryFile(filename, tmpFilename);

            if (removeChangesFile && wxFileExists(changesFilename))
            {
                wxRemoveFile(changesFilename);
            }
        }
        else
        {
            wxRemoveFile(tmpFilename);
        }
    }

    return true;
}

/*
 * AxResourceSet contains an array of integers pointing to indices in the
 * resource database.
 */

AxResourceSet::AxResourceSet(AxResourceLibrary* library)
{
    Init(); m_library = library;
}

// Init
void AxResourceSet::Init()
{
    m_library = NULL;
}

void AxResourceSet::Copy(const AxResourceSet& set)
{
    m_resources = set.m_resources;
    m_library = set.m_library;
}

bool AxResourceSet::operator==(const AxResourceSet& WXUNUSED(set)) const
{
    // TODO
    return true;
}

// Get resource
AxResource* AxResourceSet::GetResource(int idx) const
{
    //if (!m_library)
    //    return NULL;
    return m_resources[idx];
}

// Clear all
void AxResourceSet::Clear()
{
    m_resources.Clear();
}

/*
 * AxResourceLibrarySettings: resource library settings
 */

AxResourceLibrarySettings::~AxResourceLibrarySettings()
{
}

void AxResourceLibrarySettings::Init()
{
}

void AxResourceLibrarySettings::Copy(const AxResourceLibrarySettings& settings)
{
    m_mainLibraryPath = settings.m_mainLibraryPath;
    m_additionalLibraryPaths = settings.m_additionalLibraryPaths;
    m_uploadPath = settings.m_uploadPath;
    m_supportedFiles = settings.m_supportedFiles;
    m_libraryDataFile = settings.m_libraryDataFile;
}

// Get changes filename, deducing it from the library data filename
wxString AxResourceLibrarySettings::GetLibraryChangesDataFile() const
{
    if (m_libraryDataFile.IsEmpty())
        return wxEmptyString;

    wxString dataChangesFile(wxPathOnly(m_libraryDataFile));
    dataChangesFile = AxFileUtilities::AppendPaths(dataChangesFile, wxT("changes.dat"));
    return dataChangesFile;
}
